//
//  UIViewController+YHExtension.swift
//  Adorbee
//
//  Created by amovision on 2024/12/30.
//

import UIKit

// MARK: - Controller Stack Position

public enum YHControllerStackPosition: Equatable {
  case uninitialized
  case onTop
  case pushed
}

// MARK: - UIViewController+YHExtension

public extension UIViewController {
  var navigationChildren: [UIViewController] {
    navigationControllerInstance?.viewControllers ?? []
  }

  // Current window's root view controller
  // This existing method will be kept as is.
  func setAsRootController(_ animated: Bool = true) {
    (UIApplication.shared.keyWindow?.rootViewController as? UINavigationController)?.setViewControllers([self], animated: true)
  }

  func setChildControllers(_ viewControllers: [UIViewController], animated: Bool = true) {
    navigationControllerInstance?.setViewControllers(viewControllers, animated: animated)
  }

  /// Recursively finds the top-most view controller in the hierarchy.
  static func currentViewController(from root: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
    guard let root = root else { return nil }

    if let presented = root.presentedViewController {
      return currentViewController(from: presented)
    }

    if let navigationController = root as? UINavigationController {
      return currentViewController(from: navigationController.visibleViewController)
    }

    if let tabBarController = root as? UITabBarController {
      return currentViewController(from: tabBarController.selectedViewController)
    }

    return root
  }

  /// Returns the current top-most view controller, by iterating through the presentation stack to find the root presenter.
  static func currentTop() -> UIViewController? {
    guard let currentVC = currentViewController() else { return nil }
    var topMostVC = currentVC
    while let presenting = topMostVC.presentingViewController {
      topMostVC = presenting
    }
    return topMostVC
  }

  // MARK: - Dismissal Methods (Present Stack)

  /// Dismisses all view controllers presented by the receiver until the root presenter is reached.
  /// This effectively dismisses the entire present stack that the receiver is part of.
  @discardableResult
  func dismissToRootViewController(animated: Bool, completion: (() -> Void)? = nil) -> UIViewController? {
    var viewControllerToDismiss: UIViewController = self
    while let presenting = viewControllerToDismiss.presentingViewController {
      viewControllerToDismiss = presenting
    }
    viewControllerToDismiss.dismiss(animated: animated, completion: completion)
    return viewControllerToDismiss
  }

  /// Dismisses the directly presented view controller by the receiver.
  func dismissPresentedController(animated: Bool, completion: (() -> Void)? = nil) {
    if let presented = presentedViewController {
      presented.dismiss(animated: animated, completion: completion)
    }
  }

  // MARK: - Push/Pop Methods (Navigation Stack)

  /// Helper to get the navigation controller. If the receiver is a `UINavigationController`, returns itself. Otherwise, returns `self.navigationController`.
  private var navigationControllerInstance: UINavigationController? {
    return (self is UINavigationController) ? (self as? UINavigationController) : navigationController
  }

  /// Pushes a view controller onto the navigation stack.
  func push(_ viewController: UIViewController, animated: Bool = true) {
    if (navigationControllerInstance?.children.count ?? 0) > 0 {
      viewController.hidesBottomBarWhenPushed = true
    }
    navigationControllerInstance?.pushViewController(viewController, animated: animated)
  }

  /// Pops the top view controller from the navigation stack.
  func pop(animated: Bool = true) {
    navigationControllerInstance?.popViewController(animated: animated)
  }

  /// Pops to a specific view controller in the navigation stack.
  func pop(to viewController: UIViewController, animated: Bool = true) {
    navigationControllerInstance?.popToViewController(viewController, animated: animated)
  }

  /// Pops to a view controller that matches the given condition.
  /// If no view controller matches, nothing happens.
  func pop(toViewControllerMatching condition: @escaping (UIViewController) -> Bool, animated: Bool = true) {
    guard let navigationVC = navigationControllerInstance else { return }
    if let targetVC = navigationVC.viewControllers.first(where: condition) {
      navigationVC.popToViewController(targetVC, animated: animated)
    }
  }

  /// Pops to a view controller that matches the given condition.
  /// If no view controller matches and `elseToRoot` is true, pops to the root view controller.
  func pop(toViewControllerMatching condition: @escaping (UIViewController) -> Bool, elseToRoot: Bool) {
    guard let navigationVC = navigationControllerInstance else { return }
    if let targetVC = navigationVC.viewControllers.first(where: condition) {
      navigationVC.popToViewController(targetVC, animated: true)
    } else if elseToRoot {
      navigationVC.popToRootViewController(animated: true)
    }
  }

  /// Pops to the root view controller of the navigation stack.
  func popToRoot(animated: Bool = true) {
    navigationControllerInstance?.popToRootViewController(animated: animated)
  }

  // MARK: - Presentation Methods

  func present(_ viewControllerToPresent: UIViewController, isFullScreen: Bool = true, animated flag: Bool = true, completion: (() -> Void)? = nil) {
    if isFullScreen {
      viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    present(viewControllerToPresent, animated: flag, completion: completion)
  }

  // MARK: - Dismissal Methods (Presented VC)

  /// Dismisses the receiver.
  func dismiss(_ animated: Bool = true, completion: (() -> Void)? = nil) {
    dismiss(animated: animated, completion: completion)
  }

  // MARK: - Navigation Stack Manipulation

  /// Sets the view controllers for the navigation stack, keeping only those that match the condition.
  func setViewControllers(
    _ viewControllers: [UIViewController] = [],
    animated: Bool = true,
    matching condition: ((UIViewController) -> Bool)? = nil
  ) {
    guard let navigationVC = navigationControllerInstance else { return }
    let filterVCs = if let condition { navigationVC.viewControllers.filter(condition) } else { [UIViewController]() }
    let vcs = (filterVCs + viewControllers).removingDuplicates {
      type(of: $0) == type(of: $1)
    }
    guard !vcs.isEmpty else { return }
    navigationVC.setViewControllers(vcs, animated: animated)
  }

  // MARK: - Controller Stack Position Check

  /// Checks the position of a view controller of a given class within the navigation stack.
  func checkPosition<T: UIViewController>(ofClass cls: T.Type, completion: @escaping (YHControllerStackPosition, T?) -> Void) {
    guard let navigationVC = navigationControllerInstance else {
      completion(.uninitialized, nil)
      return
    }

    let targetVC = navigationVC.viewControllers.first { $0.isKind(of: cls) } as? T
    if let pvc = targetVC {
      if pvc == navigationVC.topViewController {
        completion(.onTop, pvc)
      } else {
        completion(.pushed, pvc)
      }
    } else {
      completion(.uninitialized, nil)
    }
  }
}

#if DEBUG
public extension UIViewController {
  @objc func injected() {
    viewDidLoad()
  }
}
#endif
